﻿using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;

namespace TCP_UDP
{
    public class TcpClient
    {
        public event EventHandler Connected;
        public event EventHandler<IReceiveEventArgs> Received;
        public event EventHandler Closed;
        public event EventHandler<IErrorEventArgs> ErrorOccurred;


        Socket _socket;
        SocketAsyncEventArgs _asyncEvent;
        ITcpConfiguration _tcpConfig;

        public TcpClient(ITcpConfiguration tcpConfig)
        {
            _tcpConfig = tcpConfig;
        }

        public void Init()
        {
            if (_tcpConfig?.ReceiveBufferSize < 1)
            {
                throw new ArgumentException($"{nameof(_tcpConfig.ReceiveBufferSize)} must greater than 0");
            }
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            Connect();

        }
        public void Send(byte[] buffer)
        {
            if (!_socket.Connected)
            {
                return;
            }

            var count = _socket.Send(buffer);
        }

        private void ReceiveEvent_Completed(object sender, SocketAsyncEventArgs e)
        {
            ProcessReceive();
        }

        #region 协议内部方法

        bool _isConnecting = false;
        internal void Connect()
        {
            if (_isConnecting)
            {
                return;
            }

            try
            {
                _isConnecting = true;
                if (_socket.RemoteEndPoint == null)
                {
                    _socket.Connect(_tcpConfig.RemoteEndPoint);
                }

                Connected?.Invoke(this, new IOEventArgs { LocalEndPoint = _socket.LocalEndPoint, RemoteEndPoint = _socket.RemoteEndPoint });
                Receive();
            }
            catch (Exception error)
            {
                ErrorOccurred?.Invoke(this, new IOEventArgs { Error = error, LocalEndPoint = _tcpConfig?.LocalEndPoint, RemoteEndPoint = _tcpConfig?.RemoteEndPoint });

                throw new InvalidOperationException("see inner", error);
            }
            finally
            {
                _isConnecting = false;
            }
        }

        internal void Receive()
        {
            var isPending = _socket.ReceiveAsync(_asyncEvent);
            if (!isPending)
            {
                ProcessReceive();
            }
        }
        internal void ProcessReceive()
        {
            if (_asyncEvent.SocketError == SocketError.Success && _asyncEvent.LastOperation == SocketAsyncOperation.Receive && _asyncEvent.BytesTransferred > 0)
            {
                var data = new byte[_asyncEvent.BytesTransferred];
                Array.Copy(_asyncEvent.Buffer, 0, data, 0, data.Length);
                Received?.Invoke(this, new IOEventArgs { Data = data, LocalEndPoint = _socket.LocalEndPoint, RemoteEndPoint = _asyncEvent.RemoteEndPoint });
                Receive();
            }
            else
            {
                Close();
            }
        }

        internal void Close()
        {
            var eventArgs = new IOEventArgs { LocalEndPoint = _socket.LocalEndPoint, RemoteEndPoint = _socket.RemoteEndPoint };

            _socket.Shutdown(SocketShutdown.Both);
            _socket.Close();

            Closed?.Invoke(this, eventArgs);
        }
        #endregion


        public void MonitorClose()
        {
            _asyncEvent.Completed -= ReceiveEvent_Completed;
            Close();
        }


    }
}
